from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


from widgetMessage import msgDialog
from widgetComInterface import ComInterfaceWidget
from widgetProtocol import ProtocolSelectWidget
import ctypes
import time,os,json,queue
from threading import BoundedSemaphore
from config import config
from msg import Msg
from script import Script
from log import logger

widgetHeight = 25
queueDeletegateTx = queue.Queue()
queueDeletegateRx = queue.Queue()


class ComBaseWidget(QWidget):

    def __init__(self):
        super().__init__()

        self.defaultDeviceId = 1
        
        self.cmdLastSend = 0

        # 输入框体的正则表达式
        regDevices = QRegExp()
        regDevices.setPattern("^([0-9]+-[0-9]+)|([0-9]+,?)+$")
        regValidatorDevice = QRegExpValidator(regDevices)
        regOnlyNumber = QRegExp()
        regOnlyNumber.setPattern("^([0-9])+$")
        self.regValidatorOnlyNumber = QRegExpValidator(regOnlyNumber)

        # 右侧日志框体
        self.log = QPlainTextEdit()
        self.log.setReadOnly(True)

        # 左侧=
        self.layoutLeft = QGridLayout()
        self.groupParameter = QGroupBox("命令参数")
        self.layoutCmdParameter = QGridLayout()
        self.groupParameter.setLayout(self.layoutCmdParameter)
        self.groupBaseInfo = QGroupBox("基本信息")
        self.layoutBaseInfo = QGridLayout()
        self.groupBaseInfo.setLayout(self.layoutBaseInfo)

        # combo 控件格式 增大listview的宽度
        # self.setStyleSheet("QComboBox {  min-width: 40px; }"  
        # "QComboBox QAbstractItemView::item { min-height: 40px; min-width: 60px; }")

        # 接口/协议选择
        groupInterfaceSelect = QGroupBox("接口选择")
        layoutInterfaceSelect = QGridLayout()
        self.comboInterface = ComInterfaceWidget()
        self.comboInterface.setMinimumHeight(widgetHeight)
        self.comboProtocol = ProtocolSelectWidget()
        self.comboProtocol.setMinimumHeight(widgetHeight)
        layoutInterfaceSelect.addWidget(QLabel("接口: "), 0,0,1,1)
        layoutInterfaceSelect.addWidget(self.comboInterface, 0,1,1,2)
        layoutInterfaceSelect.addWidget(QLabel("协议: "), 1,0,1,1)
        layoutInterfaceSelect.addWidget(self.comboProtocol, 1,1,1,2)
        groupInterfaceSelect.setLayout(layoutInterfaceSelect)

        # 设备列表
        self.lineDeivceList = QLineEdit()
        self.lineDeivceList.setMinimumHeight(widgetHeight)
        self.lineDeivceList.setValidator(regValidatorDevice)
        self.lineDeivceList.setPlaceholderText("1-20或者1,2,3 默认" + str(self.defaultDeviceId))

        # 设备列表
        self.lineScriptDeivceList = QLineEdit()
        self.lineScriptDeivceList.setMinimumHeight(widgetHeight)
        self.lineScriptDeivceList.setValidator(regValidatorDevice)
        self.lineScriptDeivceList.setPlaceholderText("1-20或者1,2,3 默认" + str(self.defaultDeviceId))

        # 命令选择
        self.comboCommand = QComboBox()
        self.comboCommand.setMinimumHeight(widgetHeight)
        
        listView = QListView()
        self.comboCommand.setView(listView)

        # 本地日志文件
        self.fileName = time.strftime("./log/" + "%y%m%d", time.localtime()) + "-485.log"
        self.logFile = open(self.fileName, "a+", encoding="utf8")

        # 时间间隔
        self.lineInterval = QLineEdit()
        self.lineInterval.setMinimumHeight(widgetHeight)
        self.lineInterval.setPlaceholderText("单位ms, 空=单次发送")
        self.lineInterval.setValidator(self.regValidatorOnlyNumber )
        
        # 发送
        self.buttonSend = QPushButton("发送")
        self.buttonSend.setMinimumHeight(widgetHeight)

        # 定时接收
        self.timerMain = QTimer()
        self.timerMain.start(10)

        # 定时发送
        self.timerInterval = QTimer()

        # 升级
        self.groupFileSelect = QGroupBox("文件选择")
        layoutFileSelect = QVBoxLayout()
        self.lineFileSelect = QLineEdit()
        self.lineFileSelect.setMinimumHeight(widgetHeight)
        self.lineFileSelect.setReadOnly(True)
        self.buttonSelectFile = QPushButton("选择文件")
        self.buttonSelectFile.setMinimumHeight(widgetHeight)
        # self.buttonUpdate = QPushButton("开始升级")
        # self.buttonUpdate.setMinimumHeight(widgetHeight)
        # self.buttonEcat2Flash = QPushButton("Ecat文件生成")
        # self.buttonEcat2Flash.setMinimumHeight(widgetHeight)
        layoutFileSelect.addWidget(self.lineFileSelect)
        layoutFileSelect.addWidget(self.buttonSelectFile)
        # layoutUpdate.addWidget(self.buttonUpdate)
        # layoutUpdate.addWidget(self.buttonEcat2Flash)
        self.groupFileSelect.setLayout(layoutFileSelect)


                # 运行脚本
        self.comboScript = QComboBox()
        self.comboScript.setMinimumHeight(widgetHeight)
        self.comboScript.setView(QListView())
        self.checkScriptLoop = QCheckBox("循环运行")
        self.checkScriptClearLog = QCheckBox("同时清除日志")
        self.buttonRunScript = QPushButton("启动脚本")
        self.buttonRunScript.setMinimumHeight(widgetHeight)
        groupScript = QGroupBox("脚本")
        layoutScript = QVBoxLayout()
        layoutScript.addWidget(self.lineScriptDeivceList)
        layoutScript.addWidget(self.comboScript)
        layoutScript.addWidget(self.groupFileSelect)
        layoutScript.addWidget(self.checkScriptLoop)
        layoutScript.addWidget(self.checkScriptClearLog)
        layoutScript.addWidget(self.buttonRunScript)
        groupScript.setLayout(layoutScript)

        # 清除log
        self.buttonClearLog = QPushButton("清除log")
        self.buttonClearLog.setMinimumHeight(widgetHeight)
        
        # 左侧布局
        self.layoutLeft.addWidget(QLabel("设备号"), 5,0,1,1)
        self.layoutLeft.addWidget(self.lineDeivceList, 5,1,1,2)
        self.layoutLeft.addWidget(QLabel("间隔"), 6,0,1,1)
        self.layoutLeft.addWidget(self.lineInterval, 6,1,1,2)
        self.layoutLeft.addWidget(QLabel("命令"), 7,0,1,1)
        self.layoutLeft.addWidget(self.comboCommand, 7,1,1,2)
        self.layoutLeft.addWidget(self.buttonSend, 8,0,1,3)
        self.layoutLeft.addWidget(self.groupParameter, 10, 0,1,3)
        self.layoutLeft.addWidget(groupScript, 15, 0,1,3)
        self.layoutLeft.addWidget(self.buttonClearLog, 21, 0,1,3)
        
        # 最后加入控件, 挤占控件, 将控件挤到上层 << 删除, 改用addStretch
        # reserveWidget = QWidget()
        # reserveWidget.setMinimumHeight(400)
        # self.layoutLeft.addWidget(reserveWidself.createTxMsgget, 30, 0,1,3)
        layoutLeftVertical = QVBoxLayout()
        layoutLeftVertical.addWidget(groupInterfaceSelect)
        layoutLeftVertical.addWidget(self.groupBaseInfo)
        layoutLeftVertical.addLayout(self.layoutLeft)
        layoutLeftVertical.addStretch()

        
        # 整体布局
        layout = QHBoxLayout()
        layout.addLayout(layoutLeftVertical)
        layout.addWidget(self.log)

        self.setLayout(layout)

        # 信号与槽
        self.comboProtocol.currentIndexChanged.connect(self.protocolSelect)
        self.comboCommand.currentIndexChanged.connect(self.commandSelect)
        self.comboScript.currentIndexChanged.connect(self.scriptSelect)
        self.buttonSend.clicked.connect(self.commandSend)
        self.lineDeivceList.textChanged.connect(self.singleDeviceListChange)
        self.lineScriptDeivceList.textChanged.connect(self.scriptDeviceListChange)
        self.lineInterval.textChanged.connect(self.IntervalGet)
        self.buttonSelectFile.clicked.connect(self.fileSelect)
        self.buttonClearLog.clicked.connect(self.logClear)
        self.buttonRunScript.clicked.connect(self.scriptOperation)
        self.timerInterval.timeout.connect(self.commandSendInterval)
        self.timerMain.timeout.connect(self.timerMainHandler)

        # 变量
        self.cmdParaWidgets = []
        self.baseInfoWidgets = []
        cmdSend = self.comboProtocol.currentCmdList().get("send")[self.comboCommand.currentIndex()]
        self.parameters = cmdSend.get("parameter")
        self.cmdsRecv = self.comboProtocol.currentCmdList().get("recv")  
        self.script = None

        self.deviceList = [self.defaultDeviceId]
        self.deviceListScript = [self.defaultDeviceId]
        self.interval = 0    
        self.indexDevice = 0
        self.indexDeviceScript = 0
        self.scriptIndex = 0
        self.scriptObject = None

        files = os.listdir("./script")
        for i in files:
            self.comboScript.addItem(i.split(".")[0])

        # 初始化
        self.protocolSelect()
        self.commandSelect()


    # 命令选择, 自动生成参数选择框
    def commandSelect(self):
        # 获取参数列表
        cmdSend = self.comboProtocol.currentCmdList().get("send")[self.comboCommand.currentIndex()]
        self.parameters = cmdSend.get("parameter")

        # 清除所有的参数
        for i in range(self.layoutCmdParameter.count()):
            self.layoutCmdParameter.itemAt(i).widget().deleteLater()
            self.cmdParaWidgets = []

        # 动态生成控件
        for i in range(len(self.parameters)):
            if self.parameters[i].get("combo") is not None:
                comboItems = self.parameters[i].get("combo")
                widget = QComboBox()
                widget.setView(QListView())
                for item in comboItems:
                    widget.addItem(item[0], item[1])

                # 查看是否有缓存, 如果有缓存则写入缓存
                if self.cmdCache[self.comboCommand.currentIndex()] is not None:
                    index = 0
                    for comboIndex in range(len(comboItems)):
                        if comboItems[comboIndex][1] == self.cmdCache[self.comboCommand.currentIndex()][i]:
                            index = comboIndex
                    widget.setCurrentIndex(index)
                else:
                    widget.setCurrentIndex(self.parameters[i].get("default"))
            else:   
                # 查看是否有缓存, 如果有缓存则写入缓存
                if self.cmdCache[self.comboCommand.currentIndex()] is not None:
                    widget = QLineEdit(str(self.cmdCache[self.comboCommand.currentIndex()][i]))
                else:
                    widget = QLineEdit(str(self.parameters[i].get("default")))
                widget.setAlignment(Qt.AlignHCenter)
                widget.setValidator(self.regValidatorOnlyNumber )
            widget.setMinimumHeight(widgetHeight)
            self.cmdParaWidgets.append(widget)
            self.layoutCmdParameter.addWidget(QLabel(self.parameters[i].get("tag")), i,0,1,1)
            self.layoutCmdParameter.addWidget(widget, i,1,1,2)
            
            if self.parameters[i].get("enable") is not None:
                widget.setEnabled(self.parameters[i].get("enable"))

    
    # 基本参数自动生成
    def protocolSelect(self):
        self.comboCommand.clear()
        self.cmdCache = []
        for i in self.comboProtocol.currentCmdList().get("send"):
            self.cmdCache.append(None)
            self.comboCommand.addItem(i["tag"], i["cmd"])

        # 获取参数列表
        self.baseInfo = self.comboProtocol.currentBaseInfo()
        # 清除所有的参数
        for i in range(self.layoutBaseInfo.count()):
            self.layoutBaseInfo.itemAt(i).widget().deleteLater()
            self.baseInfoWidgets = []

        # 动态生成控件
        for i in range(len(self.baseInfo)):
            if self.baseInfo[i].get("combo") is not None:
                widget = QComboBox()
                widget.setView(QListView())
                for item in self.baseInfo[i].get("combo"):
                    widget.addItem(item[0], item[1])
                widget.setCurrentIndex(self.baseInfo[i].get("default"))
            else:   
                widget = QLineEdit(str(self.baseInfo[i].get("default")))
                widget.setAlignment(Qt.AlignHCenter)
                widget.setValidator(self.regValidatorOnlyNumber )
            widget.setMinimumHeight(widgetHeight)
            self.baseInfoWidgets.append(widget)
            self.layoutBaseInfo.addWidget(QLabel(self.baseInfo[i].get("tag")), i,0,1,1)
            self.layoutBaseInfo.addWidget(widget, i,1,1,2)
            
            if self.baseInfo[i].get("enable") is not None:
                widget.setEnabled(self.baseInfo[i].get("enable"))

    # --------------------------报文收发 start ---------------------------------
    # 报文发送
    def commandSend(self):
        # 将报文以及信息送往dll处理, 获取报文实体与解析字符串
        msg = Msg(self.deviceList[0], self.comboCommand.currentData(), self.comboCommand.currentText())
        msg.setBaseValue(self.baseValueOnWidget())
        msg.setCmdValue(self.cmdValueOnWidget())
        msg.complete(self.comboProtocol.currentProfile(), "send")

        # 记录命令缓存
        self.cmdCache[self.comboCommand.currentIndex()] = self.cmdValueOnWidget()
        
        
        if self.buttonSend.text() == "发送":
            if self.interval == 0:
                queueDeletegateTx.put(msg.jsonString().encode("utf8"))
            else:
                if self.comboInterface.currentInterface().isOpen() is False:
                    msgDialog("错误", "[%s] 接口未打开" % self.comboInterface.currentInterface().tag())
                    return

                self.indexDevice = 0            # 重置索引
                self.timerInterval.start(self.interval)
                self.buttonSend.setText("停止")
                self.lineDeivceList.setEnabled(False)
                self.lineInterval.setEnabled(False)
        else:
            self.timerInterval.stop()
            self.buttonSend.setText("发送")
            self.lineDeivceList.setEnabled(True)
            self.lineInterval.setEnabled(True)

            
    # 报文定时发送
    def commandSendInterval(self):
        try:
            msg = Msg(self.deviceList[self.indexDevice], self.comboCommand.currentData(), self.comboCommand.currentText())
            msg.setBaseValue(self.baseValueOnWidget())
            msg.setCmdValue(self.cmdValueOnWidget())
            msg.complete(self.comboProtocol.currentProfile(), "send")
            queueDeletegateTx.put(msg.jsonString().encode("utf8"))

            self.indexDevice += 1
            if self.indexDevice >= len(self.deviceList):
                self.indexDevice = 0
                if(len(self.deviceList) > 1):
                    raise Exception
        except:
            self.timerInterval.stop()
            self.buttonSend.setText("发送")
            self.lineDeivceList.setEnabled(True)
            self.lineInterval.setEnabled(True)

    def txMsgHandler(self):
        try:
            txPacket = queueDeletegateTx.get_nowait()
        except queue.Empty:
            return
        
        if self.comboInterface.currentInterface().isOpen() is False:
            # msgDialog("错误", "[%s] 接口未打开" % self.comboInterface.currentInterface().tag())
            return

        # 尝试使用json解析
        result, errInfo = Msg.jsonFormatCheck(txPacket)
        if result == False:
            logger.info("[Delegate] %s" % errInfo)
            return
        
        msgInfo = json.loads(txPacket.decode("utf8"))
        msg = Msg(msgInfo["deviceId"], msgInfo["cmd"])
        msg.update(msgInfo)

        result, errInfo = msg.complete(self.comboProtocol.currentProfile(), "send")
        if result == False:
            logger.info("[Delegate] %s" % errInfo)
            return

        # 查看是否为原始数据发送
        origin = msg.obj.get("origin")
        if origin is not None:
            txMsg = bytearray(origin)
            if msg.obj.get("currentIndex") is not None and msg.obj.get("packetCount") is not None:
                txString = "向设备【%d】发送多包数据, 进度 %d/%d" % (msg.obj["deviceId"], msg.obj["currentIndex"] + 1, msg.obj["packetCount"])
            else:
                txString = None
        else:
            txMsg, txString = self.createTxMsg(msg.obj.get("deviceId"), msg.jsonString())

        self.cmdLastSend = msg.obj["cmdInfo"]["attribute"]["cmd"]
        logger.info("[%s] 发送 >> %s" % (self.comboInterface.currentInterface().tag(), self.hex(txMsg)))
        if txString is not None:
            logger.info("[%s] %s" % (self.comboInterface.currentInterface().tag(), txString))
        self.comboInterface.currentInterface().send(txMsg)
        self.logAddLine(txString, msg.obj["cmdInfo"]["attribute"])

    # 报文接收
    def rxMsgHandler(self):
        try:
            packet = self.comboInterface.currentInterface().queueRecv.get(block=False)
            logger.info("[%s] 接收 << %s" % (self.comboInterface.currentInterface().tag(), self.hex(packet)))
        except queue.Empty as e:
            return 
        
        # 如果报文合法, 则进入解析流程, 否则直接打印
        errCode, errInfo = self.isPacketLegal(packet)
        if errCode:
            cmd = self.getMsgCmd(packet)
            if self.comboProtocol.findCmd("recv", cmd) is None:
                cmd = self.cmdLastSend
            # 命令不存在直接打印原始, 命令存在则进入解析获取字符串以及解析
            cmdInfo = self.comboProtocol.findCmd("recv", cmd)

            if cmdInfo is None:
                self.logAddLine(self.hex(packet))
            else:
                rxString, rxMsgInfo, = self.analyseRxMsg(packet, cmd)
                self.logAddLine(rxString, cmdInfo)
                queueDeletegateRx.put(rxMsgInfo.encode("utf8"))
                if self.script is not None:
                    self.script.queueScriptRx.put(json.loads(rxMsgInfo))         # 填充到脚本队列中
                logger.info("[RxMsgInfo]" + rxMsgInfo)
            
        else:
            self.logAddLine(self.hex(packet))

    def timerMainHandler(self):
        self.txMsgHandler()
        self.rxMsgHandler()         # 接收
        self.scriptCheckHandler()

    # --------------------------报文收发 end ---------------------------------
    # --------------------------脚本处理 start ---------------------------------
     # 脚本启动
    def scriptOperation(self):
        if self.buttonRunScript.text() == "停止脚本":
            self.exitScript()
        else:
            # 查看端口是否打开
            if self.comboInterface.currentInterface().isOpen() is False:
                msgDialog("错误", "[%s] 接口未打开" % self.comboInterface.currentInterface().tag())
                return

            # 获取脚本
            scriptPath = "./script/" + self.comboScript.currentText() + ".json"
            self.script = Script(self.comboProtocol.currentProfile(), 
                                 self.comboProtocol.currentDll(),
                                 self.deviceListScript, 
                                 scriptPath, 
                                 self.lineFileSelect.text(),
                                 self.checkScriptLoop.isChecked())

            # 脚本检查
            err, errInfo = self.script.check()
            if  err == False:
                msgDialog("错误", errInfo)
                return 

            self.script.start()

            self.lineScriptDeivceList.setEnabled(False)
            self.lineFileSelect.setEnabled(False)
            self.checkScriptLoop.setEnabled(False)
            self.buttonSelectFile.setEnabled(False)
            self.comboScript.setEnabled(False)
            self.buttonRunScript.setText("停止脚本")
            if self.checkScriptClearLog.isChecked():
                self.logClear()

    def scriptCheckHandler(self):
        if self.script is not None:
            if self.script.isAlive() is False:
                if self.script.exitReason != "":
                    self.logAddLine("脚本异常中断 => " + self.script.exitReason)
                else:
                    self.logAddLine("脚本执行完成")
                self.exitScript()
                

    # 退出脚本
    def exitScript(self):
        if self.script is not None:
            self.script.exit()
        self.script = None
        self.scriptIndex = 0
        self.buttonRunScript.setText("启动脚本")
        self.lineScriptDeivceList.setEnabled(True)
        self.lineFileSelect.setEnabled(True)
        self.checkScriptLoop.setEnabled(True)
        self.buttonSelectFile.setEnabled(True)
        self.comboScript.setEnabled(True)

    # 脚本切换时的控件处理
    def scriptSelect(self):
        # 获取脚本createMsgByData
        filename = "./script/" + self.comboScript.currentText() + ".json"
        with open(filename, "r") as f:
            try:
                self.scriptObject = json.loads(f.read())
            except:
                msgDialog("错误", "脚本格式错误, 无法解析")
                return 
        
        if self.scriptObject.get("needFile") is not None and self.scriptObject.get("needFile") is True:
            self.groupFileSelect.setVisible(True)
        else:
            self.groupFileSelect.setVisible(False)
        

    # 文件选择
    def fileSelect(self):
        dialog = QFileDialog()
        if dialog.exec():
            self.lineFileSelect.setText(dialog.selectedFiles()[0])

    # --------------------------脚本处理 end ---------------------------------

    # 单次/单条的设备列表修改
    def singleDeviceListChange(self):
        self.indexDevice = 0
        self.deviceList = self.deviceListGet(self.lineDeivceList.text(), self.defaultDeviceId)

    # 脚本的设备列表修改
    def scriptDeviceListChange(self):
        self.indexDeviceScript = 0
        self.deviceListScript = self.deviceListGet(self.lineScriptDeivceList.text(), self.defaultDeviceId)


    # 设备列表转换
    def deviceListGet(self, content, default:None):
        deviceList = []
        try:
            if content == "":
                deviceList =  [] if default is None else [self.defaultDeviceId]
            elif '-' in content:
                deviceRange = content.split("-")
                deviceList = list(range(int(deviceRange[0]),int(deviceRange[1]) + 1))
            elif ',' in content:
                deviceList = content.split(",")
                if deviceList[-1] == '':
                    deviceList.pop(-1)
                deviceList = [int(i) for i in deviceList]
            else:
                deviceList = []
                deviceList.append(int(content))
        except:
            deviceList = [] if default is None else [self.defaultDeviceId]

        return deviceList

    # 判断是否为广播
    def isBoardcast(self):
        return (len(self.deviceList) == 1 and self.deviceList[0] == 255)

    # 间隔获取
    def IntervalGet(self):
        if self.lineInterval.text() == "":
            self.interval = 0
        else:
            self.interval = int(self.lineInterval.text())

    # 日志添加一行
    def logAddLine(self, lineString, options={}):
        if options is None:
            options = {}

        if lineString == "":
            return

        # 隐藏所有
        if options.get("hideAll") is not None and options.get("hideAll") is True:
            return
        
        # 隐藏前缀, 一般用于日志
        if options.get("hidePrefix") and options.get("hidePrefix") is True:
            content = lineString
        else:
            content = time.strftime("%H:%M:%S ", time.localtime()) + "[%s] " % (self.comboInterface.currentInterface().tag()) + lineString + "\n"

        self.log.insertPlainText(content)
        self.log.moveCursor(QTextCursor.End)
        self.logFile.write(content)



    # 16进制字符串形式
    def hex(self, packet):
        hexString = ""
        for i in packet:
            hexString += "%02X " % i
        return hexString


    def logClear(self):
        self.log.setPlainText("")


    # ---------------------------------报文编解码 start ----------------------------------------
    # 生成发送报文
    def createTxMsg(self, deviceId:int, msgJsonString):
        size = 2048
        packetBytes = (ctypes.c_ubyte * size)()
        msgString = ctypes.create_string_buffer(size, '\0')
        msgInfoJson = ctypes.create_string_buffer(bytes(msgJsonString, encoding="utf8"))

        packetLen = self.comboProtocol.currentDll().createTxMsg(packetBytes, size, msgString, size, deviceId, msgInfoJson)

        return bytes(packetBytes)[:packetLen], msgString.value.decode("utf8")
    

    # 获取报文的命令
    def getMsgCmd(self, packet):
        packetBytes = (ctypes.c_ubyte * len(packet))()
        for i in range(len(packet)):
            packetBytes[i] = packet[i]
        cmdString = ctypes.create_string_buffer(128, '\0')
        result = self.comboProtocol.currentDll().getMsgCmd(packetBytes, len(packet), cmdString, 128)
        if result == 0:
            return cmdString.value.decode("utf8")
        return ""

        # 报文合法性检查
    def isPacketLegal(self, packet):
        
        packetBytes = (ctypes.c_ubyte * len(packet))()
        for i in range(len(packet)):
            packetBytes[i] = packet[i]
        errString = ctypes.create_string_buffer(4096, '\0')

        result = self.comboProtocol.currentDll().packetLegalCheck(packetBytes, len(packet), errString, 4096)
        if result == 0:
            return True, errString.value.decode("utf8")
        return False, errString.value.decode("utf8")

    # 解析接收报文
    def analyseRxMsg(self, packet, cmd):
        packetBytes = (ctypes.c_ubyte * len(packet))()
        for i in range(len(packet)):
            packetBytes[i] = packet[i]

        msg = Msg(-1, cmd)
        msg.complete(self.comboProtocol.currentProfile(), "recv", completeValue=False)

        msgInfoString = ctypes.create_string_buffer(bytes(msg.jsonString(), encoding="utf8"))
        resultInfoBuffer = ctypes.create_string_buffer(4096, '\0')

        result = self.comboProtocol.currentDll().analyseRxMsg(packetBytes, len(packet), resultInfoBuffer, 4096, msgInfoString)

        # 解析失败, 直接返回错误
        try:
            logger.info("[RxOrigin]" +  resultInfoBuffer.value.__str__())
            msgValues = json.loads(resultInfoBuffer.value.decode("utf8"))
        except Exception as e:
            rxString += "解析失败, json格式不正确"
            msg.obj["origin"] = [int(i) for i in packet]
            return rxString, msg.jsonString()
            
        msg.update(msgValues)
        msg.complete(self.comboProtocol.currentProfile(), "recv", completeValue=False)


        # 解析成功, 处理字符串
        if msg.obj["cmdInfo"]["attribute"].get("isString") is True:
            rxString = bytearray(msg.obj["content"]).decode("utf8")
        else:
            rxString = "收到设备【%d】回复, " % msg.obj["deviceId"]
            try:
                for i in range(len(msg.obj["cmdInfo"]["attribute"]["parameter"])):
                    rxString += "%s:%s " % (msg.obj["cmdInfo"]["attribute"]["parameter"][i].get("tag"), 
                                            msg.obj["cmdInfo"]["value"][i])
            except Exception as e:
                rxString += "解析失败, 解析包中value数量不足"

        return rxString, msg.jsonString()

    # ---------------------------------报文编解码 end ----------------------------------------

    # ---------------------------------报文参数提取 start ----------------------------------------
    # 当前界面的基础信息和命令值提取
    def baseValueOnWidget(self):
        value = []
        for widget in self.baseInfoWidgets:
            if hasattr(widget, "currentText"):
                value.append(int(widget.currentData()))
            else:
                value.append(int(widget.text()))
        
        return value
    
    def cmdValueOnWidget(self):
        value = []

        for widget in self.cmdParaWidgets:
            if hasattr(widget, "currentText"):
                value.append(int(widget.currentData()))
            else:
                value.append(int(widget.text()))
        return value
    







